#include "OgreApplication.h"
#include <fmod_errors.h>

#define SOUND_ROLLOFF 0.01f


OgreApplication* SoundHandler::ogre_app = NULL;
OgreInitializeSettings* SoundHandler::ogre_init_setts = NULL;

SoundHandler::SoundHandler(OgreApplication *ogre){

	SoundHandler::ogre_app			= ogre;
	SoundHandler::ogre_init_setts	= ogre->getInitSettings();

	this->initNull();

	this->channel_count = ogre->getInitSettings()->max_sound_channel_count;

	
	this->initFMOD();
	this->initializeChannels();
	this->initializeEffectVector();
	this->initializeRootSoundNodeVector();
	this->loadStandardResources();
}

SoundHandler::~SoundHandler(){
	
	this->destroyChannels();
	
	if(this->reverb != NULL)this->reverb->release();

	this->destroyFMOD();
	
	this->destroyEffectVector();
	
	this->destroyRootSoundNodeVector();
	
}

void SoundHandler::initNull(){
	
	this->previousListenerPosition	= Ogre::Vector3(0,0,0);
	this->listener_cam				= NULL;
	this->listener_node				= NULL;
	
	this->reverb					= NULL;
	this->fmod_system				= NULL;
	this->channel_array				= NULL;
	this->channel_count				= 0;

	this->effectVector				= NULL;
	this->rootSoundNodeVector		= NULL;
	this->currentForegroundEffect	= 0;

}

void SoundHandler::loadStandardResources(){

	Ogre::ResourceGroupManager* resmgr  =	SoundHandler::ogre_app->getResourceGroupManager();	
	
	resmgr->createResourceGroup("GeneralSounds");
	resmgr->addResourceLocation(SoundHandler::ogre_init_setts->relative_path+"\\sounds\\General","FileSystem","GeneralSounds",false);
	resmgr->initialiseResourceGroup("GeneralSounds");
	resmgr->loadResourceGroup("GeneralSounds");

}

//listener position

Ogre::Vector3 SoundHandler::getListenerPosition(){
	
	Ogre::Vector3 ret = Ogre::Vector3(0,0,0);
	
	if(this->listener_cam != NULL)ret = this->listener_cam->getPosition()*SoundHandler::ogre_init_setts->METER;
	if(this->listener_node != NULL) ret = this->listener_node->_getDerivedPosition()*SoundHandler::ogre_init_setts->METER;

	return ret;
}

Ogre::Quaternion SoundHandler::getListenerOrientation(){

	Ogre::Quaternion ret = Ogre::Quaternion();

	if(this->listener_cam != NULL)ret = this->listener_cam->getOrientation();
	if(this->listener_node != NULL) ret = this->listener_node->_getDerivedOrientation();

	return ret;

}

Ogre::Vector3 SoundHandler::getListenerVelocity(unsigned long millis){

	Ogre::Vector3 vel = this->getListenerPosition() - this->previousListenerPosition;

	vel = vel* SoundHandler::ogre_init_setts->METER * 1000 / (float) millis;

	return vel;
}

bool SoundHandler::getListenerOverSS(Ogre::Vector3 velocityVec){

	float vel = velocityVec.squaredLength();
	return (vel >= SoundHandler::ogre_init_setts->SQUARED_SPEED_OF_SOUND);
}

//channel array

void SoundHandler::initializeChannels(){
	if(this->channel_array == NULL && this->channel_count > 0){
		
		this->channel_array = new SoundInstance*[this->channel_count];
			
			for(int i = 0; i < this->channel_count;i++){
				this->channel_array[i] = NULL;
			}
	}
}

void SoundHandler::destroyChannels(){

	if(this->channel_array == NULL)return;

		for(int i = 0; i < this->channel_count;i++){
			
			this->removeIndex(i);
		}

		delete [] this->channel_array;
		this->channel_array = NULL;
		
}

int SoundHandler::getNextFreeChannel(){

	for(int i = 0; i < this->channel_count;i++){
		if(this->channel_array[i] == NULL){
			return i;
			}
		}
	return -1;
}

SoundInstance* SoundHandler::get(int index){
	if(index < 0 || index >= this->channel_count)return NULL;

	return this->channel_array[index];
}

void SoundHandler::removeIndex(int index){

	SoundInstance* curr = this->get(index);

	if(curr == NULL)return;
	if(curr->isRoom()){
	
		delete ((SoundNode*)curr);

	}
	else{
	delete curr;
	}
	this->channel_array[index] = NULL;
}

void SoundHandler::set(int index, SoundInstance *inst){
	if(index < 0 || index >= this->channel_count)return;
	this->channel_array[index] = inst;
}

int SoundHandler::autoSetInFreeIndex(SoundInstance* inst){
	int index = this->getNextFreeChannel(); 
	if(index >= 0)this->set(index,inst);

	return index;
}

//effects

void SoundHandler::initializeEffectVector(){
	this->effectVector				= new std::vector<SoundEffects*>();

	SoundEffects* std_eff			= new SoundEffects("DefaultForegroundEffect");

	std_eff->isStandard				= true;
	std_eff->max_distance			= SoundHandler::ogre_init_setts->sound_max_distance;
	std_eff->min_distance			= SoundHandler::ogre_init_setts->sound_min_distance;
	std_eff->volume					= SoundHandler::ogre_init_setts->sound_volume;
	std_eff->pitch					= SoundHandler::ogre_init_setts->sound_pitch;
	std_eff->distance_effect_scale  = SoundHandler::ogre_init_setts->sound_distance_effect_scale;
	std_eff->rolloff_scale			= SoundHandler::ogre_init_setts->sound_rolloff_scale;
	std_eff->doppler_effect_scale	= SoundHandler::ogre_init_setts->sound_doppler_effect_scale;

	this->effectVector->push_back(std_eff);
}

void SoundHandler::destroyEffectVector(){

	if(this->effectVector == NULL)return;

	for(unsigned int i = 0; i < this->effectVector->size();i++){
		delete this->effectVector->at(i);
	}
	
	this->effectVector->clear();
	delete this->effectVector;
	this->effectVector = NULL;
}

//root_sound_nodes

void SoundHandler::initializeRootSoundNodeVector(){

	this->rootSoundNodeVector = new std::vector<RootSoundNode*>();
}

void SoundHandler::destroyRootSoundNodeVector(){
	
	if(this->rootSoundNodeVector == NULL)return;
	
	for(unsigned int i = 0; i < this->rootSoundNodeVector->size();i++){
		delete this->rootSoundNodeVector->at(i);
	}
	
	this->rootSoundNodeVector->clear();
	delete this->rootSoundNodeVector;
	this->rootSoundNodeVector = NULL;
}


void SoundHandler::playSonicBoom(Ogre::Vector3 position, SonicBoomType type){
	this->createStaticSound(this->getSonicBoomName(type),"GeneralSounds",position);
}

//fmod methods

void SoundHandler::initFMOD(){

FMOD_RESULT result;


   // Create the main system object.
   result = FMOD::System_Create(&this->fmod_system);
   if (result != FMOD_OK)
	   OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "FMOD error! (" + Ogre::StringConverter::toString(result) + "): " + FMOD_ErrorString(result), "SoundHandler::Initialize");

   result = this->fmod_system->init(this->ogre_init_setts->max_sound_channel_count, FMOD_INIT_SOFTWARE_OCCLUSION, 0);  // Initialize FMOD.
   if (result != FMOD_OK)
	   OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "FMOD error! (" + Ogre::StringConverter::toString(result) + "): " + FMOD_ErrorString(result), "SoundHandler::Initialize");


   this->fmod_system->set3DSettings(this->ogre_init_setts->sound_doppler_effect_scale,1.0f, SOUND_ROLLOFF);
        
   result = this->fmod_system->setFileSystem(&fmodFileOpenCallback, &fmodFileCloseCallback, &fmodFileReadCallback, &fmodFileSeekCallback,NULL,NULL, 2048);
   
   if (result != FMOD_OK)
      OGRE_EXCEPT(Ogre::Exception::ERR_INTERNAL_ERROR, "FMOD error! (" + Ogre::StringConverter::toString(result) + "): " + FMOD_ErrorString(result), "SoundHandler::Initialize");


   Ogre::LogManager::getSingleton().logMessage("SoundHandler Initialized");

   this->fmod_system->createReverb(&this->reverb);
	
				FMOD_REVERB_PROPERTIES p   = FMOD_PRESET_ARENA;
				      
				//reverb->setProperties(&p);
                 
                  //FMOD_VECTOR pos = { 0.0f, 0.0f, 0.0f };
                  //reverb->set3DAttributes(&pos, 30.0f, 500.0f);
				  
				 // reverb->setActive(true);

						//this->fmod_system->setReverbProperties(&p);
					this->fmod_system->setReverbAmbientProperties(&p);

				
}

void SoundHandler::destroyFMOD(){

	this->fmod_system->release();
	delete this->fmod_system;

}

FMOD_RESULT SoundHandler::fmodFileOpenCallback(const char *fileName, int unicode, unsigned int *filesize, void **handle, void **userdata)
   {
   SoundInstance *soundInstance;


   assert(fileName);


   soundInstance = (SoundInstance *)fileName;
   assert(soundInstance->fileArchive);


   *handle = (void *)soundInstance;
   *userdata = NULL;


   soundInstance->setStreamPointer(soundInstance->getFullPath()->open(soundInstance->getName()));
   if (soundInstance->getStreamPointer().isNull())
      {
      *filesize = 0;
      return FMOD_ERR_FILE_NOTFOUND;
      }


   *filesize = (unsigned int)soundInstance->getStreamPointer()->size();
   return FMOD_OK;
   }




FMOD_RESULT SoundHandler::fmodFileCloseCallback(void *handle, void *userdata)
   {
   return FMOD_OK;
   }




FMOD_RESULT SoundHandler::fmodFileReadCallback(void *handle, void *buffer, unsigned int sizeBytes, unsigned int *bytesRead, void *userData)
   {
   SoundInstance *soundInstance;


   soundInstance = (SoundInstance *)handle;
   *bytesRead = (unsigned int)soundInstance->getStreamPointer()->read(buffer, (size_t)sizeBytes);
   if (*bytesRead == 0)
      return FMOD_ERR_FILE_EOF;


   return FMOD_OK;
   }




FMOD_RESULT SoundHandler::fmodFileSeekCallback(void *handle, unsigned int pos, void *userdata)
   {
   SoundInstance *soundInstance;


   soundInstance = (SoundInstance *)handle;
   soundInstance->getStreamPointer()->seek((size_t)pos);
   return FMOD_OK;
   }

FMOD::Channel* SoundHandler::GetSoundChannel(int channelIndex){
	
	FMOD::Channel*	sound = NULL;

	this->fmod_system->getChannel(channelIndex,&sound);

	return sound;

}

void SoundHandler::CreateSound(SoundInstance *sound){
	if(sound == NULL)return;
	//if(sound->getStreamPointer().isNull() || sound->getSoundType() == INVALID_SOUND)return;
	
	FMOD_RESULT    result;
	
	FMOD::Sound*   temp_sound = NULL;

	if(sound->isRoom()){
		if(sound->isLooped()){
			result = this->fmod_system->createStream((const char *)sound, FMOD_LOOP_NORMAL | FMOD_3D | FMOD_HARDWARE, 0, &temp_sound);
		}
		else{
			result = this->fmod_system->createStream((const char *)sound, FMOD_3D, 0, &temp_sound);
		}
	}

	if(sound->isScreen()){
		if(sound->isLooped()){
			result = this->fmod_system->createStream((const char *)sound, FMOD_LOOP_NORMAL | FMOD_2D | FMOD_HARDWARE, 0, &temp_sound);
		}
		else{
			result = this->fmod_system->createStream((const char *)sound, FMOD_DEFAULT, 0, &temp_sound);
		}
	}

   if (result != FMOD_OK)
      {
		  Ogre::LogManager::getSingleton().logMessage("SoundHandler::CreateSound could not load sound '" + sound->getName() + "'  FMOD Error:" + FMOD_ErrorString(result));
      return;
      }

   sound->setFMODSound(temp_sound);
   float dist = 0;
	
   if(sound->isRoom())dist = (((SoundNode*)sound)->getPosition().distance(this->getListenerPosition())*SoundHandler::ogre_init_setts->METER);

   sound->create(dist);
   
   this->autoSetInFreeIndex(sound);

   return;
}

int SoundHandler::PlaySound(SoundInstance* sound,int index){

	if(sound == NULL)return -1;
	
	FMOD_RESULT    result;
	FMOD_VECTOR    initialPosition;
	FMOD::Channel *channel;

	

	result = this->fmod_system->getChannel(index,&channel);

	if(result != FMOD_OK)return -1;

	result = this->fmod_system->playSound(FMOD_CHANNEL_FREE,sound->getFMODSound(),true,&channel);
	
	if(result != FMOD_OK) return -1;

	int channelind = -1;

	channel->getIndex(&channelind);

	sound->setChannel(channelind);

	if(sound->isRoom()){

		Ogre::Vector3	curpos = ((SoundNode*)sound)->getPosition();

		initialPosition.x = curpos.x;
		initialPosition.y = curpos.y;
		initialPosition.z = curpos.z;
		channel->set3DAttributes(&initialPosition, NULL);

	}
	this->channel_array[index] = NULL;
	this->set(channelind,sound);


	
	
	/*
	FMOD::DSP* dspecho;
	
	this->fmod_system->createDSPByType(FMOD_DSP_TYPE_SFXREVERB, &dspecho);

	dspecho->setParameter(FMOD_DSP_SFXREVERB_ROOMSIZE, 250.0f);

	channel->addDSP(dspecho,0);
*/
	
	channel->setPaused(false);

	sound->play();

	return channelind;
}

void SoundHandler::StopSound(SoundInstance *sound){

	if(sound == NULL)return;

	if(sound->getChannel() == -1 ||sound->getFMODSound() == NULL)return;


	this->GetSoundChannel(sound->getChannel())->stop();

	this->removeIndex(sound->getChannel());

}

void SoundHandler::StopSound(int channelIndex){
	this->StopSound(this->get(channelIndex));
}

//manaul methods

void SoundHandler::update(unsigned long millis){
	
	Ogre::Vector3	listener_pos	= this->getListenerPosition();
	Ogre::Quaternion listener_or	= this->getListenerOrientation();
	Ogre::Vector3   listener_vel    = this->getListenerVelocity(millis);
	bool			player_over_ss  = this->getListenerOverSS(listener_vel);
 
   FMOD_VECTOR    listenerPosition;
   FMOD_VECTOR    listenerForward;
   FMOD_VECTOR    listenerUp;
   FMOD_VECTOR    listenerVelocity;
   Ogre::Vector3  vectorVelocity;
   Ogre::Vector3  vectorForward;
   Ogre::Vector3  vectorUp;


  
   vectorForward = listener_or.zAxis();
   vectorForward.normalise();


   vectorUp = listener_or.yAxis();
   vectorUp.normalise();

   listenerPosition.x = listener_pos.x;
   listenerPosition.y = listener_pos.y;
   listenerPosition.z = listener_pos.z;


   listenerForward.x = vectorForward.x;
   listenerForward.y = vectorForward.y;
   listenerForward.z = vectorForward.z;


   listenerUp.x = vectorUp.x;
   listenerUp.y = vectorUp.y;
   listenerUp.z = vectorUp.z;


   listenerVelocity.x = listener_vel.x;
   listenerVelocity.y = listener_vel.y;
   listenerVelocity.z = listener_vel.z;

   Ogre::Plane player_plane(listener_pos,listener_vel.perpendicular(),listener_vel.perpendicular().crossProduct(listener_vel));

	


   // update 'ears'
   this->fmod_system->set3DListenerAttributes(0, &listenerPosition, &listenerVelocity, &listenerForward, &listenerUp);
   this->fmod_system->update();

			unsigned int size = this->rootSoundNodeVector->size();

			for(unsigned int i = 0; i < size;i++){
			
				RootSoundNode*	current = this->rootSoundNodeVector->at(i);
				
				bool should_delete = this->updateRootSoundNode(current,player_plane,listener_vel,player_over_ss,millis);
				
				if(should_delete){
					delete current;
					current = NULL;
					this->rootSoundNodeVector->erase(this->rootSoundNodeVector->begin()+i);i--;size--;
				}
			}
			
			for(int i = 0;i < this->channel_count;i++){
				if(this->channel_array[i] == NULL)continue;
				this->channel_array[i]->updateTimer(millis);
				
				if(this->channel_array[i]->isReadyToPlay()){
					
					this->PlaySound(this->channel_array[i],i);continue;

				}

				if(this->channel_array[i]->isReadyToDelete()){
					
					this->removeIndex(i);continue;

				}

				FMOD::Channel* channel = this->GetSoundChannel(i);


				SoundEffects* sumeff = new SoundEffects(*this->effectVector->at(this->currentForegroundEffect));
				
				if(this->channel_array[i]->getEffect() != NULL)*sumeff+= (*this->channel_array[i]->getEffect());
				
				if(this->channel_array[i]->isRoom()){
					if(((SoundNode*) this->channel_array[i])->getEffect() != NULL) *sumeff += *((SoundNode*) this->channel_array[i])->getEffect();
				}

					
				if(this->channel_array[i]->isScreen())channel->setVolume(sumeff->volume);
				channel->set3DMinMaxDistance(sumeff->min_distance,sumeff->max_distance);
				channel->set3DDopplerLevel(sumeff->doppler_effect_scale);
				

				if(this->channel_array[i]->isScreen())continue;
				
				Ogre::Vector3 pos = ((SoundNode*)this->channel_array[i])->getPosition();
				Ogre::Vector3 vel = Ogre::Vector3(0,0,0);
					
				if(((SoundNode*)this->channel_array[i])->getParent() == NULL)continue;

				if(((SoundNode*)this->channel_array[i])->getParent() != NULL)vel = ((SoundNode*)this->channel_array[i])->getParent()->getVelocityVector();
				

				FMOD_VECTOR pos_f;
				pos_f.x = pos.x;
				pos_f.y = pos.y;
				pos_f.z = pos.z;
				FMOD_VECTOR pos_v;
				pos_v.x = vel.x;
				pos_v.y = vel.y;
				pos_v.z = vel.z;

				channel->set3DAttributes(&pos_f,&pos_v);
				

				float distance_percent = ((SoundNode*)this->channel_array[i])->getParent()->getMainNode()->_getDerivedPosition().distance(this->getListenerPosition())*SoundHandler::ogre_init_setts->METER/sumeff->max_distance;
				
				if( ((SoundNode*)this->channel_array[i])->getParent()->isSuperSonic() || ((SoundNode*)this->channel_array[i])->getParent()->isPlayerSSMuted() ){
					channel->setVolume(0.0f);
				}
				else{
					channel->setVolume(sumeff->volume);
				}

				//channel->set3DOcclusion(sumeff->distance_effect_scale+sumeff->distance_effect_scale*((SoundNode*)this->channel_array[i])->getParent()->getMainNode()->_getDerivedPosition().distance(this->getListenerPosition())*SoundHandler::ogre_init_setts->METER/sumeff->max_distance,0);
				
				float occpercent =  sumeff->distance_effect_scale+sumeff->distance_effect_scale*distance_percent;

				channel->set3DOcclusion(occpercent,occpercent);
				
				sumeff->isStandard = false;
				delete sumeff;
			}


	this->previousListenerPosition = listener_pos;
}

bool SoundHandler::updateRootSoundNode(RootSoundNode *rootnode,Ogre::Plane player_plane,Ogre::Vector3 player_velocity, bool player_ss ,unsigned long millis){

	float distance_to_player = this->getListenerPosition().distance(rootnode->getMainNode()->_getDerivedPosition())*SoundHandler::ogre_init_setts->METER;
	
	if(rootnode->isReadyToDelete(distance_to_player))return true;
	
	if(player_ss){
		rootnode->calcPlayerSSMuted(player_plane,player_velocity);
	}
	else{
		rootnode->resetPlayerSSMute();
	}

	rootnode->updateTimesAndVelocitys(this->getListenerPosition(),distance_to_player,millis);
	
	

	return false;
}

Ogre::String SoundHandler::getSonicBoomName(SonicBoomType type){

	switch(type){
			case BULLET_BOOM	:return "bullet_sboom.wav";break;
			case ARTILLERY_BOOM	:return "artillery_sboom.wav";break;
			case S_PLANE_BOOM	:return "s_plane_sboom.wav";break;
			case B_PLANE_BOOM	:return "b_plane_sboom.wav";break;
		default					:return "s_plane_sboom.wav";
	}

}

void SoundHandler::setListenerCamera(Ogre::Camera *cam){
	this->listener_node = NULL;
	this->listener_cam	= cam;
}

void SoundHandler::setListenerNode(Ogre::SceneNode *node){
	this->listener_cam = NULL;
	this->listener_node = node;
}


SoundInstance* SoundHandler::createScreenSound(Ogre::String name, Ogre::String resGroupName, bool looped, SoundEffects *effect){

	SoundInstance* created = NULL;

	if(looped){
	created = new SoundInstance(name,resGroupName,LOOPED_STREAM_SOUND);
	}
	else{
	created = new SoundInstance(name,resGroupName,STREAM_SOUND);
	}

	created->setEffect(effect);

	this->CreateSound(created);

	return created;
}

SoundInstance* SoundHandler::createSound(Ogre::String name, Ogre::String resGroupName, Ogre::Vector3 position, Ogre::SceneNode *attached, bool looped, RootSoundDeleteCondition delcon, SoundEffects *effect){

	RootSoundNode* parent = NULL;
	SoundNode* created = NULL;

	parent = new RootSoundNode(delcon,position,attached,effect);

	
	created = new SoundNode(name,resGroupName,looped,parent);
	
	parent->add(created);

	created->setEffect(effect);
	

	this->CreateSound(created);
	this->rootSoundNodeVector->push_back(parent);

	return created;


}

SoundInstance* SoundHandler::createStaticSound(Ogre::String name, Ogre::String resGroupName, Ogre::Vector3 position, bool looped, RootSoundDeleteCondition delcon, SoundEffects *effect){
	std::cout << "Pos: " << position << "\n";
	return this->createSound(name,resGroupName,position,NULL,looped,delcon,effect);

}